package com.googlecode.agscrum.model.entity;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.Lob;
import javax.persistence.ManyToMany;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.OrderBy;
import javax.persistence.SequenceGenerator;
import javax.persistence.Transient;

import org.hibernate.annotations.Cascade;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.Configurable;

import com.googlecode.agscrum.model.exceptions.GScrumDomainException;
import com.googlecode.agscrum.model.exceptions.MessageFactory;
import com.googlecode.agscrum.model.exceptions.GScrumDomainException.TypeMessage;
import com.googlecode.agscrum.model.validator.ValidatorUtil;

@Entity
@NamedQueries(value = {
		@NamedQuery(name = "Product.searchQuery", query = "select new Product(p.id, p.name) from Product p"),
		@NamedQuery(name = "Product.duplicateValidate", query = "select count(*) as total from Product p where p.name=:name") })
@Configurable(autowire = Autowire.BY_NAME)
public class Product {
	@Id
	@GeneratedValue(generator = "product_sequence", strategy = GenerationType.SEQUENCE)
	@SequenceGenerator(name = "product_sequence", sequenceName = "product_sequence")
	@Column(name = "id_product")
	private Long id;
	@Column(nullable = false, length = 15)
	private String name;
	@Column(nullable = true)
	@Lob
	private String description;

	private boolean active = true;

	@OneToMany(cascade = CascadeType.ALL, mappedBy = "product")
	@JoinColumn(name = "id_product")
	@OrderBy("id ASC")
	@Cascade(value = org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
	private List<Sprint> sprints;
	@OneToMany(cascade = CascadeType.ALL, mappedBy = "product")
	@JoinColumn(name = "id_product")
	private List<Item> items;
	@OneToMany(mappedBy = "product", cascade = CascadeType.ALL)
	@JoinColumn(name = "id_product")
	private List<Document> documents;
	@ManyToMany
	@JoinTable(name = "PRODUCT_TEAM", joinColumns = @JoinColumn(name = "id_product", referencedColumnName = "id_product"), inverseJoinColumns = @JoinColumn(name = "id_team", referencedColumnName = "id_team"))
	private List<Team> teams;

	public Product() {
		// TODO Auto-generated constructor stub
	}

	public Product(Long id, String name) {
		super();
		this.id = id;
		this.name = name;
	}

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getDescription() {
		return description;
	}

	public void setDescription(String description) {
		this.description = description;
	}

	@Transient
	private MessageFactory messageFactory;

	public void setMessageFactory(MessageFactory messageFactory) {
		this.messageFactory = messageFactory;
	}

	@Transient
	private ValidatorUtil validatorUtil;

	public void setValidatorUtil(ValidatorUtil validatorUtil) {
		this.validatorUtil = validatorUtil;
	}

	public void changeTeams(List<Team> selectedTeams)
			throws GScrumDomainException {
		if (selectedTeams == null || selectedTeams.size() == 0) {
			throw new GScrumDomainException(messageFactory.createMessage(
					"product.teams.required", null, TypeMessage.ERROR));
		} else {
			if (teams == null) {
				teams = new ArrayList<Team>();
			} else {
				teams.clear();
			}
			teams.addAll(selectedTeams);
		}
	}

	public List<Team> getTeams() {
		if (teams != null)
			return Collections.unmodifiableList(teams);
		else
			return teams;
	}

	public List<Item> getItems() {
		if (items != null)
			return Collections.unmodifiableList(items);
		else
			return items;
	}

	public List<Sprint> getSprints() {
		if (sprints != null)
			return Collections.unmodifiableList(sprints);
		else
			return sprints;
	}

	public void saveItem(Item item) throws GScrumDomainException {
		validatorUtil.validate(item);
		if (items == null) {
			items = new ArrayList<Item>();
		}
		if (item.getId() == null) {
			items.add(item);
		} else {
			if (items.contains(item)) {
				items.set(items.indexOf(item), item);
			}
		}
	}

	public void removeItem(Item item) {
		if (items != null) {
			if (items.contains(item)) {
				items.remove(item);
			}
		}
	}

	public void saveSprint(Sprint sprint) throws GScrumDomainException {
		validatorUtil.validate(sprint);
		if (sprints == null) {
			sprints = new ArrayList<Sprint>();
		}
		if (sprint.getId() == null) {
			sprints.add(sprint);
		} else {
			if (sprints.contains(sprint)) {
				sprints.set(sprints.indexOf(sprint), sprint);
			}
		}
	}

	public void removeSprint(Sprint sprint) {
		if (sprints != null) {
			if (sprints.contains(sprint)) {
				if (sprint.isRemoved()) {
					sprints.remove(sprint);
				} else {
					throw new GScrumDomainException(messageFactory
							.createMessage("product.sprint.remove", null,
									TypeMessage.ERROR));
				}
			}
		}
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((id == null) ? 0 : id.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Product other = (Product) obj;
		if (id == null) {
			if (other.id != null)
				return false;
		} else if (!id.equals(other.id))
			return false;
		return true;
	}
}
